package com.ruoyi.system;


import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import java.util.Properties;
import java.util.regex.Matcher;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.ParameterMapping;
import org.apache.ibatis.mapping.ParameterMode;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Plugin;
import org.apache.ibatis.plugin.Signature;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.type.TypeHandlerRegistry;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.stereotype.Component;

/**
 * 性能分析拦截器，用于输出每条 SQL 语句及其执行时间
 * 去除sql语句有问号
 * @author hu hongtao
 * @since 2019年12月16日23:51:14
 */
@Intercepts({
   @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}),
   @Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class})
})
public class PerformanceInterceptor  implements Interceptor {

   private static Log logger = LogFactory.getLog(PerformanceInterceptor.class);

   private static final DateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

   @Override
   public Object intercept(Invocation invocation) throws Throwable {
      MappedStatement mappedStatement = (MappedStatement) invocation.getArgs()[0];
      Object parameterObject = null;
      if (invocation.getArgs().length > 1) {
         parameterObject = invocation.getArgs()[1];
      }
      // 计算执行 SQL 耗时
      long start = System.currentTimeMillis();
      Object result = invocation.proceed();
      String statementId = mappedStatement.getId();
      BoundSql boundSql = mappedStatement.getBoundSql(parameterObject);
      Configuration configuration = mappedStatement.getConfiguration();
      String sql = getSql(boundSql, parameterObject, configuration);
      if (logger.isInfoEnabled()) {
         long timing = System.currentTimeMillis() - start;
         StringBuilder formatSql = new StringBuilder()
            .append("\n")
            .append("Execute SQL Time: ").append(timing)
            .append(" ms - ID: ").append(statementId)
            .append("\n").append("Execute SQL: ")
            .append(sql).append("\n");
         logger.info(formatSql.toString());
      }
      return result;
   }

   @Override
   public Object plugin(Object target) {
      if (target instanceof Executor) {
         return Plugin.wrap(target, this);
      }
      return target;
   }

   @Override
   public void setProperties(Properties properties) {
   }

   private String getSql(BoundSql boundSql, Object parameterObject, Configuration configuration) {
      String sql = boundSql.getSql().replaceAll("[\\s]+", " ");
      List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
      TypeHandlerRegistry typeHandlerRegistry = configuration.getTypeHandlerRegistry();
      if (parameterMappings != null) {
         for (int i = 0; i < parameterMappings.size(); i++) {
            ParameterMapping parameterMapping = parameterMappings.get(i);
            if (parameterMapping.getMode() != ParameterMode.OUT) {
               Object value;
               String propertyName = parameterMapping.getProperty();
               if (boundSql.hasAdditionalParameter(propertyName)) {
                  value = boundSql.getAdditionalParameter(propertyName);
               } else if (parameterObject == null) {
                  value = null;
               } else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
                  value = parameterObject;
               } else {
                  MetaObject metaObject = configuration.newMetaObject(parameterObject);
                  value = metaObject.getValue(propertyName);
               }
               sql = replacePlaceholder(sql, value);
            }
         }
      }
      return sql;
   }
   private String replacePlaceholder(String sql, Object propertyValue) {
      String result;
      if (propertyValue != null) {
         if (propertyValue instanceof String) {
            result = "'" + propertyValue + "'";
         } else if (propertyValue instanceof Date) {
            result = "'" + DATE_FORMAT.format(propertyValue) + "'";
         } else {
            result = propertyValue.toString();
         }
      } else {
         result = "null";
      }
      return sql.replaceFirst("\\?", Matcher.quoteReplacement(result));
   }


   @Component
   public static class StartSysListener implements ApplicationListener<ContextRefreshedEvent> {

      @Override
      public void onApplicationEvent(ContextRefreshedEvent event) {
         if(event.getApplicationContext().getDisplayName().contains("org.springframework.boot.web.servlet")){
            SqlSessionFactory sqlSessionFactory = event.getApplicationContext().getBean(SqlSessionFactory.class);
            this.addMyInterceptor(sqlSessionFactory);
         }
      }

      private void addMyInterceptor(SqlSessionFactory sqlSessionFactory) {
         logger.debug("添加自定义Mybatis SQL拦截器.");
         sqlSessionFactory.getConfiguration().addInterceptor(new PerformanceInterceptor());
      }

   }
}